In this article:
Understanding How Export Works
Export in SciChart WPF can be divided into two categories, depending on the internal process used to generate the output:
- Export “As-Is” — used to export an on-screen chart exactly as it appears (WYSIWYG – What You See Is What You Get). This approach uses the native WPF rendering pipeline and is generally straightforward, robust, and error-resistant.
- Export with Custom Parameters — used when exporting a chart that is either not currently visible on the screen or needs to be modified before export. This includes changes such as resolution, image size, quality settings, or visual alterations (e.g., hiding elements, changing themes, or adding annotations).
This export path involves cloning the chart in memory and rendering it off-screen. Internally, it relies on XML serialization and deserialization to create an in-memory copy of the chart. Because of this, it is inherently more nuanced and error-prone compared to the simpler "As-Is" export.
To learn how to address common issues related to this export method, please continue reading the sections below.
Common Issues When Exporting with Custom Parameters
When using the custom export path, several issues may arise due to its reliance on serialization and off-screen rendering. Below are some of the most common problems:
- Exception during export. This often indicates a serialization error or misconfigured export parameters.
- The exported image appears empty. This usually is caused by the chart not being properly initialized off-screen.
- Missing chart elements. Components such as Legends, Annotations, or RenderableSeries may disappear. This could be due to incorrect serialization, missing styles, broken bindings, or unavailable data contexts.
- Misplaced components. Elements like Legends or Annotations may appear in the wrong location.
- Incorrect visual appearance. Includes issues such as incorrect colors, fonts, or background styling.
Please continue reading below for a step-by-step guide to identifying the root cause and applying common solutions.
Step-by-Step Troubleshooting Guide
1. Handling Export Errors
Wrap the export logic in a try-catch block to catch any exceptions, identify the exact cause of the failure, and inspect the call stack:
| Handling Export Errors |
Copy Code |
|---|---|
try { // Triggers clone + serialize (custom size specified) var targetSize = new Size(1920, 1080); sciChartSurface.ExportToFile( "chart-1920x1080.png", ExportType.Png, useXamlRenderSurface: false, exportedSize: targetSize ); } catch (SciChart.Charting.Common.Helpers.ExportException ex) { // Inspect details to identify what in the graph failed to serialize Debug.WriteLine("[SciChart] Export failed: " + ex.Message); Debug.WriteLine("[SciChart] Inner: " + ex.InnerException?.Message); } | |
2. Checking for Serialization Errors in Output
Export-related errors — whether crashes or handled exceptions appear in the Output window during export. Always check the Output for detailed information about what went wrong.
For example, the error message may indicate:
- The stage at which the failure occurred (e.g., serialization, deserialization, or property assignment)
- The property that caused the error
As an example, consider the following common scenario. There is a chart with a custom ChartModifier that has to be exported. This ChartModifier is derived from ChartModifierBase, which includes a custom property like HorizontalLineSource of type ObservableCollection<object>. Due to how XML serialization works, this may result in an error during export with parameters. A typical error message is shown in the screenshots below:


3. Overriding CreateCloneOfSurfaceInMemory
Export with custom parameters always invokes CreateCloneOfSurfaceInMemory(). During this process:
- The input
SciChartSurfaceis serialized to XML - Then deserialized into a new instance
- Special cases such as text scaling, styling, and DataContext assignment are handled
- The cloned chart is then rendered off-screen
You can override this method to inspect or modify any step in the export process. Start by overriding the method as follows, and use the resulting cloned SciChartSurface as a base for further customization:
| Overriding CreateCloneOfSurfaceInMemory() method |
Copy Code |
|---|---|
using System.Windows; using SciChart.Charting.Visuals; public class SciChartSurfaceEx : SciChartSurface { // Called by SciChart during clone-based export (custom size, XPS, etc.) protected override SciChartSurfaceBase CreateCloneOfSurfaceInMemory(Size newSize) { // Always start from the default clone logic return base.CreateCloneOfSurfaceInMemory(newSize); } } | |
4. Checking Serialized Chart as a String
You can serialize a chart to a string and inspect the output to ensure all relevant properties and values are properly captured.
To do this, use the following approach:
| Checking Serialized Chart as a String |
Copy Code |
|---|---|
public class SciChartSurfaceEx : SciChartSurface { protected override SciChartSurfaceBase CreateCloneOfSurfaceInMemory(Size newSize) { var stream = new MemoryStream(); var serializer = new XmlSerializer(typeof(SciChartSurface)); // this = SciChartSurfaceEx serializer.Serialize(stream, this); stream.Position = 0; var serializedString = new StreamReader(stream).ReadToEnd(); // Perform base cloning functionality return base.CreateCloneOfSurfaceInMemory(newSize); } } | |
This code can be placed anywhere in your application — even outside CreateCloneOfSurfaceInMemory(), — and is useful for reviewing the serialized output. A sample result might look like this:
| Serialized Chart Check Output |
Copy Code |
|---|---|
<?xml version="1.0" encoding="utf-8"?> <SciChartSurface Theme="" HasYAxis="true" HasXAxis="true" Name="sciChart"> <RenderableSeries> <FastLineRenderableSeries Type="SciChart.Charting.Visuals.RenderableSeries.FastLineRenderableSeries, SciChart.Charting, Version=8.9.0.28766, Culture=neutral, PublicKeyToken=b55dd9efe817e823" StrokeThickness="2" Stroke="<Color xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">#FFADFF2F</Color>" Foreground="<SolidColorBrush xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">#FFA6A7AC</SolidColorBrush>" UseLayoutRounding="True" Name="lineRenderSeries" /> </RenderableSeries> <XAxes Type="SciChart.Charting.Model.AxisCollection, SciChart.Charting, Version=8.9.0.28766, Culture=neutral, PublicKeyToken=b55dd9efe817e823"> <NumericAxis Type="SciChart.Charting.Visuals.Axes.NumericAxis, SciChart.Charting, Version=8.9.0.28766, Culture=neutral, PublicKeyToken=b55dd9efe817e823" IsPrimaryAxis="True" DrawMajorBands="True" FlipCoordinates="True" UseLayoutRounding="True" VisibleRange="SciChart.Data.Model.DoubleRange,-0.9998,10.9978" GrowBy="SciChart.Data.Model.DoubleRange,0.1,0.1" /> </XAxes> <YAxes Type="SciChart.Charting.Model.AxisCollection, SciChart.Charting, Version=8.9.0.28766, Culture=neutral, PublicKeyToken=b55dd9efe817e823"> <NumericAxis Type="SciChart.Charting.Visuals.Axes.NumericAxis, SciChart.Charting, Version=8.9.0.28766, Culture=neutral, PublicKeyToken=b55dd9efe817e823" IsPrimaryAxis="True" DrawMajorBands="True" UseLayoutRounding="True" VisibleRange="SciChart.Data.Model.DoubleRange,-6.437263483092142,6.437263483092144" GrowBy="SciChart.Data.Model.DoubleRange,0.5,0.5" /> </YAxes> </SciChartSurface> | |
5. Checking the Cloned Chart
Inspect the final cloned SciChartSurface instance in memory during Debug to verify that all expected components (e.g., axes, series, annotations, modifiers) are present. This is useful to verify whether all properties were preserved during cloning and nothing important is missing:
| Checking the Cloned Chart |
Copy Code |
|---|---|
public class SciChartSurfaceEx : SciChartSurface { protected override SciChartSurfaceBase CreateCloneOfSurfaceInMemory(Size newSize) { // Perform base cloning functionality var cloned = base.CreateCloneOfSurfaceInMemory(newSize); return cloned; } } | |
Typical Solutions and Workarounds
If you encounter serialization-related exceptions in the Output during export, check whether any of your custom classes used in chart rendering violate the limitations of .NET XML serialization. Common limitations include:
- A public parameterless constructor is required
- Only public properties with both getter and setter are serialized
- Collections must implement
IEnumerable Dictionary<TKey, TValue>is not supported as a serializable property type
If any of these issues are detected, adjust your class definitions accordingly to ensure compatibility with the serialization process.
Excluding Properties from Serialization
If the export fails due to non-serializable properties, you can exclude those properties from the serialization pipeline using one of the following attributes:
[ShouldNotSerialize]– A SciChart-specific attribute that excludes a member from SciChart’s internal chart serialization process.[XmlIgnore]– A standard .NET attribute that prevents a property from being included in XML serialization entirely.
If your application uses XML serialization elsewhere, you may want to avoid using [XmlIgnore], as it will also prevent the property from being serialized outside of SciChart. In such cases, prefer using only [ShouldNotSerialize] to exclude the property only from SciChart’s export pipeline.
If you don’t rely on XML serialization in other parts of your app, you can safely apply both attributes:
| Excluding Properties from Serialization |
Copy Code |
|---|---|
[ShouldNotSerialize] [XmlIgnore] public string MyCustomText { get => GetValue(MyCustomText Property); set => SetValue(MyCustomText Property, value); } | |
Applying Properties After Serialization
If the properties you excluded in the previous step are still required for chart rendering, you can apply them manually after the chart is recreated in memory, effectively bypassing the internal serialization process.
To do this, override the CreateCloneOfSurfaceInMemory method and assign the required properties to the cloned chart instance within that method.
For example, if you have a complex property such as ExtraData that cannot be easily serialized or deserialized, you can exclude it from the serialization process, then apply it manually inside the CreateCloneOfSurfaceInMemory override:
| Applying Properties After Serialization |
Copy Code |
|---|---|
protected override SciChartSurfaceBase CreateCloneOfSurfaceInMemory(Size newSize) { // Get and cache 'non-serializable' custom property from custom TextAnnotation var myCustomAnnotation = this.Annotations.FirstOrDefault((x) => x.GetType() == typeof(MyCustomTextAnnotation)); ObservableCollection<string> cachedExtraData = myCustomAnnotation.ExtraData; // NULL-ing custom property to avoid crashes during clonning process myCustomAnnotation.ExtraData = null; // Perform base cloning functionality var cloned = base.CreateCloneOfSurfaceInMemory(newSize); // Add cached annotation to the cloned surface var clonedCustomAnnotation = ((SciChartSurface)cloned).Annotations.FirstOrDefault(x => x.GetType() == typeof(MyCustomTextAnnotation)); clonedCustomAnnotation.ExtraData = cachedExtraData; // Return property back to the annotation in 'real' SciChartSurface myCustomAnnotation.ExtraData = cachedExtraData; // Return cloned surface return cloned; } | |
Implementing IXmlSerializable for Custom Classes
As an alternative to using attributes, you can implement the IXmlSerializable interface in your custom classes to gain full control over how objects are serialized to and deserialized from XML. This approach is especially useful when the default .NET XML serializer cannot handle specific data structures, or when you need to conform to a custom XML schema.
The core methods you must implement are:
WriteXml(XmlWriter writer)– Defines how the object's data is written to XML. You manually write each required element or attribute in the desired format.ReadXml(XmlReader reader)– Defines how the object is reconstructed from XML. You manually read and assign values to restore the object's internal state.GetSchema()– Returns anXmlSchemathat describes the XML structure. In most cases, this should return null as custom schemas are rarely required.
By implementing these methods, you can fully customize the XML output and input, allowing you to support complex properties, custom namespaces, or non-standard XML formats that are otherwise not supported by default serialization.
Issues with Missing Styles
If you discover that a style is missing during export, you can override the CreateCloneOfSurfaceInMemory method, locate the required style, and apply it manually to the cloned SciChartSurface:
| Applying missing Style manually |
Copy Code |
|---|---|
protected override SciChartSurfaceBase CreateCloneOfSurfaceInMemory(Size newSize) { // Get and cache style that is not cloning correctly var styleToCopy = this.RenderableSeries[0].Style; // Perform base cloning functionality var cloned = base.CreateCloneOfSurfaceInMemory(newSize); // Attach cached style to RenderableSeries ((SciChartSurface)cloned).RenderableSeries[0].Style = styleToCopy; // Return cloned surface return cloned; } | |
Alternatively, instead of reassigning the original style, you can manually clone the style and apply the new instance to your cloned elements. This can be done in one of two ways:
- By copying each setter and trigger from the original style
- By using XAML-based cloning, via
XamlWriter.Save(...)andXamlReader.Read(...)
This approach is particularly useful when applying styles to cloned RenderableSeries or any other SciChart element:
| Cloning the Style |
Copy Code |
|---|---|
protected override SciChartSurfaceBase CreateCloneOfSurfaceInMemory(Size newSize) { // Perform base cloning functionality var cloned = base.CreateCloneOfSurfaceInMemory(newSize); // Creating new Styles for series via copying original one // and sets it to a cloned elements ((SciChartSurface)cloned).RenderableSeries[0].Style = NewStyleCopySetters(this.RenderableSeries[0].Style); ((SciChartSurface)cloned).RenderableSeries[1].Style = NewStyleXamlCopy(this.RenderableSeries[1].Style); // Return cloned surface return cloned; } public static Style NewStyleCopySetters(Style source) { if (source == null) return null; var copy = new Style(source.TargetType, source.BasedOn); foreach (var sb in source.Setters) { if (sb is Setter s) { copy.Setters.Add(new Setter(s.Property, s.Value)); } } return copy; } public static Style NewStyleXamlCopy(Style source) { if (source == null) return null; var xaml = XamlWriter.Save(source); using var sr = new StringReader(xaml); using var xr = XmlReader.Create(sr); return (Style)XamlReader.Load(xr); } | |
Handling Text Scaling in Exported Charts
When exporting charts, you may notice that text elements — such as axis labels, annotations, or legends, — do not always scale as expected. This is because SciChart’s text scaling relies on the ratio between the actual surface size and the target export size.
By default, the scale factor is calculated as follows:
| Scale Factor calculation |
Copy Code |
|---|---|
var scaleFactorX = size.Width / ActualWidth; var scaleFactorY = size.Height / ActualHeight; var scaleFactorXy = (scaleFactorX + scaleFactorY) * 0.5; | |
While this approach works well in most scenarios, there may be edge cases where text does not scale appropriately.
Solution: Override GetScaleFactorXy()
The method responsible for calculating the scale factor, GetScaleFactorXy(), is virtual and can be overridden in a custom SciChartSurface.
By overriding this method, you can implement custom text scaling logic, much like you would override CreateCloneOfSurfaceInMemory() for export customization:
| Implementing custom text scaling |
Copy Code |
|---|---|
public class CustomSciChartSurface : SciChartSurface { protected override double GetScaleFactorXy(Size size) { // Example: prioritize X scaling double scaleFactorX = size.Width / ActualWidth; double scaleFactorY = size.Height / ActualHeight; // Custom rule: use max instead of average return Math.Max(scaleFactorX, scaleFactorY); // Original // return (scaleFactorX + scaleFactorY) * 0.5; } } | |
Configuring Fallback Options for Export
When exporting with custom parameters, the process may fail due to issues encountered during export.
To handle such cases gracefully, wrap the export call in a try-catch block, catch the resulting ExportException, inspect the cause, and fallback to the more robust “As-Is” export path as a fallback strategy.
This ensures that export still succeeds, even if advanced export with customization fails:
| Configuring Fallback Options |
Copy Code |
|---|---|
try { // Triggers clone + serialize (custom size specified) var targetSize = new Size(1920, 1080); sciChartSurface.ExportToFile( "chart-1920x1080.png", ExportType.Png, useXamlRenderSurface: false, exportedSize: targetSize ); } catch (SciChart.Charting.Common.Helpers.ExportException ex) { // Inspect details to identify what in the graph failed to serialize Debug.WriteLine("[SciChart] Export failed: " + ex.Message); Debug.WriteLine("[SciChart] Inner: " + ex.InnerException?.Message); // Fallback: export 'as-is' (no clone; most robust path) sciChartSurface.ExportToFile( "chart-fallback.png", ExportType.Png, useXamlRenderSurface: false, exportedSize: null ); } | |